/* 
 * bottomhalf.c - Top and bottom half interrupt handling 
 * 
 * Based upon the RPi example by Stefan Wendler (devnull@kaltpost.de) 
 * from: 
 *    https://github.com/wendlers/rpi-kmod-samples 
 * 
 * Press one button to turn on an LED and another to turn it off 
 */

#include <linux/delay.h>
#include <linux/gpio.h>
#include <linux/interrupt.h>
#include <linux/module.h>
#include <linux/printk.h>
#include <linux/init.h>
#include <linux/version.h>

#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 10, 0)
#define NO_GPIO_REQUEST_ARRAY
#endif

/* Macro DECLARE_TASKLET_OLD exists for compatibility. 
 * See https://lwn.net/Articles/830964/ 
 */
#ifndef DECLARE_TASKLET_OLD
#define DECLARE_TASKLET_OLD(arg1, arg2) DECLARE_TASKLET(arg1, arg2, 0L)
#endif

static int button_irqs[] = { -1, -1 };

/* Define GPIOs for LEDs. 
 * TODO: Change the numbers for the GPIO on your board. 
 */
static struct gpio leds[] = { {4, GPIOF_OUT_INIT_LOW, "LED 1"} };

/* Define GPIOs for BUTTONS 
 * TODO: Change the numbers for the GPIO on your board. 
 */
static struct gpio buttons[] = {
	{17, GPIOF_IN, "LED 1 ON BUTTON"},
	{18, GPIOF_IN, "LED 1 OFF BUTTON"},
};

/* Tasklet containing some non-trivial amount of processing */
static void bottomhalf_tasklet_fn(unsigned long data)
{
	pr_info("Bottom half tasklet starts\n");
	/* do something which takes a while */
	mdelay(500);
	pr_info("Bottom half tasklet ends\n");
}

static DECLARE_TASKLET_OLD(buttontask, bottomhalf_tasklet_fn);

/* interrupt function triggered when a button is pressed */
static irqreturn_t button_isr(int irq, void *data)
{
	/* Do something quickly right now */
	if (irq == button_irqs[0] && !gpio_get_value(leds[0].gpio))
		gpio_set_value(leds[0].gpio, 1);
	else if (irq == button_irqs[1] && gpio_get_value(leds[0].gpio))
		gpio_set_value(leds[0].gpio, 0);

	/* Do the rest at leisure via the scheduler */
	tasklet_schedule(&buttontask);

	return IRQ_HANDLED;
}

static int __init bottomhalf_init(void)
{
	int ret = 0;
	pr_info("%s\n", __func__);

	/* register LED gpios */
	ret = gpio_request_array(leds, ARRAY_SIZE(leds));
	if (ret) {
		pr_err("Unable to request GPIOs for LEDs: %d\n", ret);
		return ret;
	}

	/* register BUTTON gpios */
	ret = gpio_request_array(buttons, ARRAY_SIZE(buttons));
	if (ret) {
		pr_err("Unable to request GPIOs for BUTTONs: %d\n", ret);
		goto fail1;
	}
	pr_info("Current button1 value: %d\n", gpio_get_value(buttons[0].gpio));

	ret = gpio_to_irq(buttons[0].gpio);
	if (ret < 0) {
		pr_err("Unable to request IRQ: %d\n", ret);
		goto fail2;
	}
	button_irqs[0] = ret;
	pr_info("Successfully requested BUTTON1 IRQ # %d\n", button_irqs[0]);

	ret = request_irq(button_irqs[0], button_isr,
			  IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
			  "gpiomod#button1", NULL);
	if (ret) {
		pr_err("Unable to request IRQ: %d\n", ret);
		goto fail2;
	}

	ret = gpio_to_irq(buttons[1].gpio);
	if (ret < 0) {
		pr_err("Unable to request IRQ: %d\n", ret);
		goto fail2;
	}
	button_irqs[1] = ret;
	pr_info("Successfully requested BUTTON2 IRQ # %d\n", button_irqs[1]);

	ret = request_irq(button_irqs[1], button_isr,
			  IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
			  "gpiomod#button2", NULL);
	if (ret) {
		pr_err("Unable to request IRQ: %d\n", ret);
		goto fail3;
	}

	return 0;

/* cleanup what has been setup so far */
 fail3:
	free_irq(button_irqs[0], NULL);

 fail2:
	gpio_free_array(buttons, ARRAY_SIZE(leds));

 fail1:
	gpio_free_array(leds, ARRAY_SIZE(leds));

	return ret;
}

static void __exit bottomhalf_exit(void)
{
	int i;
	pr_info("%s\n", __func__);

	/* free irqs */
	free_irq(button_irqs[0], NULL);
	free_irq(button_irqs[1], NULL);

	/* turn all LEDs off */
	for (i = 0; i < ARRAY_SIZE(leds); i++)
		gpio_set_value(leds[i].gpio, 0);

	/* unregister */
	gpio_free_array(leds, ARRAY_SIZE(leds));
	gpio_free_array(buttons, ARRAY_SIZE(buttons));
}

module_init(bottomhalf_init);
module_exit(bottomhalf_exit);

MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Interrupt with top and bottom half");
